/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package researchgrants.parts.LoggedData.panels.trees;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.util.Collection;
import javax.swing.event.ListSelectionEvent;
import researchgrants.parts.LoggedData.panels.LoggedDataPanels;
import java.awt.Component;
import java.awt.event.ActionListener;
import java.util.Iterator;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ListSelectionModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionListener;
import researchgrants.parts.GrantRequest.nsfcode.NsfCode;

/**
 * This class allows an extending class to be displayed as a tree for selection.
 * As a LoggedDataPanels, this allows to save and load the value previously selected and support both the viewing of the selected value and the changing (or editing, as defined below) of the value, by selecting something else.
 * Tree selection means that you can browse upward in the tree, and downward in the tree, and navigate anywhere in the tree.
 * To use this class, an extending class should implement the requirements specified in LoggedDataTreeInterface, which is implementation dependant.
 * @author DOStudent1
 */
public abstract class LoggedDataPanelsTree extends LoggedDataPanels implements LoggedDataTreeInterface, ListSelectionListener {
    // because the list is being repopulated, this flag supresses the changes done while changing the content of the list
    private boolean isChanging=false;

    // initializes the entire tree
    private TreeNode<TreeableLoggedData> allTree=getTree();
    // a reference to the current location within the tree. Used for navigation.
    private TreeNode<TreeableLoggedData> currTreeNode=null;
    // the value currently selected (a leaf node). null otherwise (when located on non-leaf nodes).
    private TreeNode<TreeableLoggedData> currValue=null;

    // global-style compoenets
    private JLabel lblCurrentSelectedValue;
    private JButton btnGoToParent;
    private JList fldList;

    // The view panel
    // Retreives and displays the path from the tree root to the selected value
    @Override
    public Component getViewComponent() {
        validateDataToDisplay();
        JLabel lblViewData=new JLabel();
        String valueId=getData().value();
        if (!valueId.equals("")) {
            // get the value's text if previously data had been saved
            TreeNode<TreeableLoggedData> selectedNode=findDescendantIdInAllTree(valueId);
            lblViewData.setText(getTreePathValue(selectedNode));
        }
        return(lblViewData);
    }
    
    // a method to retreive the path from the tree root (excluding the root) to a given TreeNode
    private String getTreePathValue(TreeNode<TreeableLoggedData> node) {
        if (!node.hasParent()) {
            // the root node - no path from the root (excluding the root)
            return("Location: ...");
        } else {
            return(getTreePathValueInner(node) + "(" + node.getValue().toTreeId() + ")");
        }
    }
    private String getTreePathValueInner(TreeNode<TreeableLoggedData> node) {
        final String seperator="->";
        String res="";
        TreeNode<TreeableLoggedData> currNode=node;
        while (currNode.getParent()!=null) {
            res=currNode.getValue().toTreeValue()+ seperator + res;
            currNode=currNode.getParent();
        }
        if (!res.equals("")) {
            res=res.substring(0, res.length()-seperator.length());
        }
        return(res);
    }

    // Finds a descendant by it's TreeableLoggedData's TreeId in the entire tree
    // returns null if non found
    private TreeNode<TreeableLoggedData> findDescendantIdInAllTree(String requestedTreeId) {
        return(findDescendantIdInSubTree(allTree, requestedTreeId));
    }

    private TreeNode<TreeableLoggedData> findDescendantIdInSubTree(TreeNode<TreeableLoggedData> subTree, String requestedTreeId) {
        for (Iterator<TreeNode<TreeableLoggedData>> it = subTree.sonsNodeCollection().iterator(); it.hasNext();) {
            TreeNode<TreeableLoggedData> treeNode = it.next();
            if (treeNode.getValue().toTreeId().equals(requestedTreeId)) {
                return(treeNode);
            } else {
                TreeNode<TreeableLoggedData> indirectSon=findDescendantIdInSubTree(treeNode, requestedTreeId);
                if (indirectSon!=null) {
                    return(indirectSon);
                }
            }
        }
        return(null);
    }
    

    // The Edit panel
    // Allows browsing throughout the tree.
    // Only when a leaf can be selected
    @Override
    public Component getEditComponent() {
        JPanel pnlContent=new JPanel(new BorderLayout(0, 0));
        JPanel pnlContent2=new JPanel(new BorderLayout(0, 0));

        // adds the list to browse down the tree and select values
        fldList=new JList();
        fldList.addListSelectionListener(this);
        fldList.setVisibleRowCount(4);
        fldList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        pnlContent.add(pnlContent2, BorderLayout.NORTH);
        JScrollPane scrollPane=new JScrollPane(fldList, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        pnlContent.add(scrollPane, BorderLayout.CENTER);

        // adds the label for the selected value
        lblCurrentSelectedValue=new JLabel("");
        pnlContent2.add(lblCurrentSelectedValue, BorderLayout.CENTER);
        
        // adds the button to browse up the tree
        btnGoToParent=new JButton("Up");
        btnGoToParent.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                goToParent();
            }
        });
        pnlContent2.add(btnGoToParent, BorderLayout.EAST);

        // preparing initial values and display
        initDisplay();

        return(pnlContent);
    }
    
    // displays the content
    private void initDisplay() {
        isChanging=true;
        if (hasDataInitialized() && !getData().value().equals("")) {
            // starts at the previously selected node
            TreeNode<TreeableLoggedData> startValue=findDescendantIdInAllTree(getData().value());
            // setting the parent to navigate to populate the list
            setCurrentValue(startValue.getParent());
            // setting the leaf to select it
            setCurrentValue(startValue);

            // when the first value is implicitly selected, need to physically select it in the list. 
            fldList.setSelectedValue(startValue.getValue().toTreeValue(), true);
        } else {
            // starts at the top of the tree
            setCurrentValue(allTree);
        }
        isChanging=false;
    }

    // setting the navigation and values to the given TreeNode
    private void setCurrentValue(TreeNode<TreeableLoggedData> newValue) {
        if (newValue.sonsNodeCollection().size()!=0) {
            // has sons - allow navigation in this TreeNode
            currValue=null;
            currTreeNode=newValue;
        } else {
            // has no sons - a leaf - select it
            currValue=newValue;
        }

        // allow to go up, unless when the root node is the current node
        btnGoToParent.setEnabled(currTreeNode.hasParent()); 
        
        if (currValue==null) {
            // displays the current location
            lblCurrentSelectedValue.setText("Location: " + getTreePathValue(currTreeNode));
            displayCurrentList();
        } else {
            // displays the current selection
            lblCurrentSelectedValue.setText("Selection: " + getTreePathValue(currValue));
        }
    }

    // displays a list of the Nodes below the current Node to select from or navigate to.
    private void displayCurrentList() {
        fldList.setSelectedIndices(new int[]{});
        DefaultListModel listModel=new DefaultListModel();
        Collection<TreeNode<TreeableLoggedData>> sonsCollection = currTreeNode.sonsNodeCollection();
        for (Iterator<TreeNode<TreeableLoggedData>> it = sonsCollection.iterator(); it.hasNext();) {
            TreeableLoggedData treeableLoggedData = it.next().getValue();
            listModel.addElement(treeableLoggedData.toTreeValue());
        }
        fldList.setModel(listModel);
    }

    // When the up button is pressed
    private void goToParent() {
        isChanging=true;
        // navigating upwards
        setCurrentValue(currTreeNode.getParent());
        isChanging=false;
        // notifies listeners about the change of status
        notifyAllChangeListeners(new ChangeEvent(this));
    }

    // When a selection is made in the list
    @Override
    public void valueChanged(ListSelectionEvent e) {
        if (!e.getValueIsAdjusting() && !isChanging) {
            // will not accept changes during transition
            isChanging=true;
            if (fldList.getSelectedIndex()==-1) {
                // unselecting the selection - result in reselecting the current node (not the leaf, but its parent)
                setCurrentValue(currTreeNode);
            } else {
                // a selection is made
                // locating the TreeNode that was selected
                String selectedElement = (String)fldList.getModel().getElementAt(fldList.getSelectedIndex());
                TreeNode<TreeableLoggedData> newSubTree=null;
                Collection<TreeNode<TreeableLoggedData>> sonsNodeCollection = currTreeNode.sonsNodeCollection();
                for (Iterator<TreeNode<TreeableLoggedData>> it = sonsNodeCollection.iterator(); it.hasNext() && newSubTree==null;) {
                    TreeNode<TreeableLoggedData> treeNode = it.next();
                    if (treeNode.getValue().toTreeValue().equals(selectedElement)) {
                        newSubTree=treeNode;
                    }
                }
                if (newSubTree==null) {
                    throw new RuntimeException("Could not find value selected in list");
                }
                // setting it as the new selection
                setCurrentValue(newSubTree);
            }
            isChanging=false;
            // notifies listeners about the change of status
            notifyAllChangeListeners(new ChangeEvent(this));
        }
    }
    
    // get the value
    @Override
    public String getEdittedValue() {
        if (currValue==null) {
            // no leaf selection is made, return an empty value
            return("");
        } else {
            // a leaf is selected, return the id of it
            return(currValue.getValue().toTreeId());
        }
    }
    
    // If a leaf is selected, it is the value. 
    // If not a leaf is selected, it is an empty selection.
    @Override
    public boolean isEmpty() {
        if (currValue==null) {
            // no leaf selection is made, return an empty value
            return(true);
        } else {
            // a leaf is selected, return the id of it
            return(false);
        }
    }
    
    // the object is always valid. Either a leaf is selected and there is a value, or no leaf is selected and the value is empty.
    @Override
    public boolean isValid() {
        return(true);
    }

}
